package com.caishi.lkx.exam.api;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.caishi.lkx.common.AdminBaseApi;
import com.caishi.lkx.common.context.RequireContext;
import com.caishi.lkx.common.context.UserContext;
import com.caishi.lkx.common.ienum.DeviceType;
import com.caishi.lkx.common.role.Role;
import com.caishi.lkx.common.role.Roles;
import com.caishi.lkx.exam.mapper.ExamMapper;
import com.caishi.lkx.exam.model.CategoryModel;
import com.caishi.lkx.exam.model.ExamModel;
import com.caishi.lkx.exam.model.PkRecords;
import com.caishi.lkx.exam.model.ProblemsModel;
import com.caishi.lkx.exam.service.*;
import com.caishi.lkx.exam.service.impl.ExamServiceImpl;
import com.caishi.lkx.exam.service.impl.IndustryServiceImpl;
import com.caishi.lkx.handler.PkWebSocketHandler;
import com.caishi.lkx.user.service.IAgentUserService;
import com.caishi.lkx.user.service.IGroupService;
import com.zzw.common.annotation.ApiResult;
import com.zzw.common.threadpool.SysThreadPool;
import com.zzw.common.Wrappers;
import com.zzw.common.entity.impl.BaseEntity;
import com.zzw.common.utils.CommonUtil;

import com.caishi.lkx.exam.dto.UserPermissionDto;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.socket.WebSocketSession;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

import static com.zzw.common.cache.CacheConstants.BIG_UP_UP;


@Tag(name = "考试相关")
@Slf4j
@RestController
@RequestMapping("/exam/exam")
public class ExamApi extends AdminBaseApi<IExamService, ExamModel, String> {

    @Resource
    private IExamService examService;

    @Resource
    private IIndustryService industryService;

    @Resource
    private ICategoryService categoryService;
    @Resource
    private IGroupService groupService;

    @Resource
    private ExamMapper examMapper;

    @Resource
    private IAgentUserService agentUserService;
    @Resource
    private IProblemService problemService;
    @Override
    public IExamService loadService() {
        return examService;
    }
    @Resource
    private UserContext userContext;
    @Resource
    private PkWebSocketHandler webSocketHandler;
    @Resource
    private IPkRecordsService pkRecordsService;

    @PutMapping("/update")
    @Override
    @Transactional(rollbackFor = Exception.class)
    @CacheEvict(value = BIG_UP_UP, allEntries = true)
    public ExamModel update(@RequestBody ExamModel entity) {
        var old = examService.getById(entity.getId());
        if (!old.getIndustryId().equals(entity.getIndustryId())) {
            categoryService.getMapper().update(null, Wrappers.<CategoryModel>lambdaUpdate()
                    .set(CategoryModel::getIndustryId, entity.getIndustryId())
                    .eq(CategoryModel::getExamId, entity.getId())
            );
        }
        return super.update(entity);
    }

    @Override
    protected List<ExamModel> listCover(List<ExamModel> result, JSONObject otherParam) {
        boolean haveIndustryName = otherParam.getBool("haveIndustryName", false);
        boolean haveCategoryCount = otherParam.getBool("haveCategoryCount", false);
        if (haveIndustryName || haveCategoryCount) {
            result.parallelStream()
                    .forEach(e -> {
                        if (haveIndustryName ) {
                            e.setIndustryName(industryService.queryName(e.getIndustryId()));
                        }
                        if (haveCategoryCount) {
                            e.setCategoryCount(
                                    categoryService.selectCount(categoryService.wrappers().eq(CategoryModel::getExamId, e.getId()).apply("attribute & 1 = 1")).intValue()
                            );
                        }
                    });
        }
        return super.listCover(result, otherParam);
    }

    @GetMapping("exam-by-industryId-app")
    @Roles
    @RequireContext({com.caishi.lkx.common.context.Role.APP, com.caishi.lkx.common.context.Role.CHANNEL,com.caishi.lkx.common.context.Role.PC})
    @Operation(summary = "根据行业获取考试")
    public List<ExamModel> getExamByIndustryId(@RequestParam(defaultValue = "1000") Integer size, @RequestParam(required = true) String industryId, @NotNull JSONObject otherParam) {
        var res =loadService().selectList(Wrappers.<ExamModel>lambdaQuery().eq(ExamModel::getIndustryId, industryId));
        var groupId = context.getGroupId();
        if(groupId != null && groupId > 0L){
            List<String> haveExamRoleList =   groupService.getById(groupId).getPermissionMap().get(0).getExamActionIds();
            if(!haveExamRoleList.isEmpty())
                return res.stream().filter(exam->haveExamRoleList.contains(exam.getId())).collect(Collectors.toList());
        }

        return res;
    }
    @GetMapping("exam-by-pc")
    @Roles
    @RequireContext({ com.caishi.lkx.common.context.Role.PC})
    @Operation(summary = "获取所有考试-PC")
    public List<ExamModel> getExams(@RequestParam(defaultValue = "1000") Integer size) {
        var res =loadService().selectList(Wrappers.<ExamModel>lambdaQuery());
        var groupId = context.getGroupId();
        if(groupId != null && groupId > 0L){
            List<String> haveExamRoleList =   groupService.getById(groupId).getPermissionMap().get(0).getExamActionIds();
            if(!haveExamRoleList.isEmpty())
                return res.stream().filter(exam->haveExamRoleList.contains(exam.getId())).collect(Collectors.toList());
        }
        return res;
    }
    @GetMapping("exam-by-agent")
    @Roles
    @Operation(summary = "获取所有考试-agent")
    public List<ExamModel> getExamsByAgent(@RequestParam(defaultValue = "1000") Integer size, @RequestParam(required = true) String industryId) {
        var res =loadService().selectList(Wrappers.<ExamModel>lambdaQuery().eq(ExamModel::getIndustryId, industryId));
        var groupId = agentUserService.getById(userContext.currentUserId()).getBindGroupId();
        if(groupId != null && groupId > 0L){
            List<String> haveExamRoleList =   groupService.getById(groupId).getPermissionMap().get(0).getExamActionIds();
            if(!haveExamRoleList.isEmpty())
                return res.stream().filter(exam->haveExamRoleList.contains(exam.getId())).collect(Collectors.toList());
        }
        return res;
    }
    @GetMapping("exam-by-industryId")
    @Roles
    @Operation(summary = "根据行业获取考试")
    public List<ExamModel> getExamByIndustryId(@RequestParam(defaultValue = "1000") Integer size, @RequestParam(required = true) String industryId) {
      return loadService().selectList(Wrappers.<ExamModel>lambdaQuery().eq(ExamModel::getIndustryId, industryId));
    }


    @PostMapping("/generate-package")
    @Roles(Role.admin)
    public void generatePackage(@RequestParam String examId) {
        ((ExamServiceImpl) examService).autoGenerateProduct(examId);
    }


    @Override
    protected LambdaQueryWrapper<ExamModel> baseWrapper(ExamModel model, JSONObject otherParam) {
        String searchKey = otherParam.getStr("searchKey");
        String examType = otherParam.getStr("examType");
        var res = super.baseWrapper(model.clearEmptyStringField("attribute"), otherParam)
                .last(otherParam.get("attribute") != null, "and attribute & " + otherParam.get("attribute") + "=" + otherParam.get("attribute"))
                .like(StrUtil.isNotBlank(searchKey), ExamModel::getName, searchKey)
                .eq(ExamModel.GJTK.equals(examType), ExamModel::getIndustryId, ExamModel.GJTK)
                .ne(ExamModel.ZY.equals(examType), ExamModel::getIndustryId, ExamModel.GJTK);
        if (ExamModel.GJTK.equals(model.getIndustryId())) {
            var permission = groupPermission();
            if (permission != null) {
                if (permission.isEmpty()) res.eq(BaseEntity::getId, "noId");
                else res.in(BaseEntity::getId, permission);
            }
        }
        return res;
    }

    private List<String> groupPermission() {
        var dt = userContext.currentTokenData().getDevice();
        if (dt != DeviceType.admin && !userContext.loginStatus()) {
            return examService.groupPermission(userContext.currentLocalGroupId(), userContext.currentGroupId(), userContext.currentUserId());
        }
        return null;
    }

    private void examAuthFlag(UserPermissionDto<String> userExam, List<ExamModel> res) {
        if (userExam == null || userExam.getAll().isEmpty()) return;
        var g = userExam.getGroup().toArray(String[]::new);
        var u = userExam.getUser().toArray(String[]::new);
        for (var item : res) {
            byte r = CommonUtil.binarySearch(u, item.getId()) ? (byte) 0x01 : 0x00;
            r |= CommonUtil.binarySearch(g, item.getId()) ? 0x02 : 0x00;
            item.setAuthFlag(r);
        }
    }

    //======================pc端数据查询============================

    @GetMapping("/index/searchExam")
    @Operation(summary = "pc首页搜索考试")
    @Parameters({
            @Parameter(name = "groupId", description = "当前ip/经纬度所处机构id"),
            @Parameter(name = "examType", description = "职业资格:zhiye 高教题库:gjtk"),
            @Parameter(name = "searchKey", description = "搜索关键字"),
    })
    @Roles
    public Object searchExam(@RequestParam(defaultValue = ExamModel.ZY) String examType, String searchKey) throws ExecutionException, InterruptedException {
        var userId = userContext.currentUserId();
        var localGroupId = userContext.currentLocalGroupId();
        var userGroupId = userContext.currentGroupId();

       // var examPermission = userContext.loginStatus() ? null : groupService.userGroupEnablePermission(localGroupId, userGroupId, userId);
        var userLogin = userContext.loginStatus();
        List<String> permissionIds = null;
//        var p = examPermission == null ? null : examPermission.stream()
//                .filter(v -> v.getType() == (ExamModel.ZY.equals(examType) ? GroupPermission.Type.zhiye : GroupPermission.Type.gjtk))
//                .findFirst()
//                .orElse(null);
//        if (p != null) {
//            permissionIds = p.getActionIds();
//        }
        if (permissionIds != null && permissionIds.isEmpty()) return null;
        List<String> finalPermissionIds = permissionIds == null ? null : permissionIds.stream().sorted(String::compareTo).toList();
        List<ExamModel> exams;
        List<CategoryModel> categories;
        if (ExamModel.ZY.equals(examType)) {
            var categoriesFuture = StrUtil.isEmpty(searchKey) ? null : SysThreadPool.submit(() -> categoryService.selectList(categoryService.wrappers()
                    .in(finalPermissionIds != null && !userLogin, CategoryModel::getIndustryId, finalPermissionIds)
                    .ne(CategoryModel::getIndustryId, ExamModel.GJTK)
                    .eq(CategoryModel::getShowState,1)
                    .like(StrUtil.isNotBlank(searchKey), CategoryModel::getName, searchKey)
                    .orderByAsc(CategoryModel::getSort)
            ));
            exams = examService.selectList(examService.wrappers()
                    .in(finalPermissionIds != null && !userLogin, ExamModel::getIndustryId, finalPermissionIds)
                    .ne(ExamModel::getIndustryId, ExamModel.GJTK)
                    .like(StrUtil.isNotBlank(searchKey), ExamModel::getName, searchKey)
            );
            categories = categoriesFuture == null ? new LinkedList<>() : categoriesFuture.get();
        } else if (ExamModel.GJTK.equals(examType)) {
            var categoriesFuture = StrUtil.isEmpty(searchKey) ? null : SysThreadPool.submit(() -> categoryService.selectList(categoryService.wrappers()
                    .in(finalPermissionIds != null && !userLogin, CategoryModel::getExamId, finalPermissionIds)
                    .eq(CategoryModel::getIndustryId, ExamModel.GJTK)
                    .eq(CategoryModel::getShowState,1)
                    .like(StrUtil.isNotBlank(searchKey), CategoryModel::getName, searchKey)
                    .orderByAsc(CategoryModel::getSort)
            ));
            exams = examService.selectList(examService.wrappers()
                    .in(finalPermissionIds != null && !userLogin, ExamModel::getId, finalPermissionIds)
                    .eq(ExamModel::getIndustryId, ExamModel.GJTK)
                    .like(StrUtil.isNotBlank(searchKey), ExamModel::getName, searchKey)
            );
            categories = categoriesFuture == null ? new LinkedList<>() : categoriesFuture.get();
        } else {
            return null;
        }
        var examIds = exams.stream().map(BaseEntity::getId).sorted(String::compareTo).toList();
        var categoryExamIds = categories.stream().map(CategoryModel::getExamId).distinct().toList();
        var cExamIds = new LinkedList<String>();
        if (StrUtil.isNotBlank(searchKey)) {
            var examIdsArr = examIds.toArray(new String[0]);
            for (var i : categoryExamIds) {
                if (!CommonUtil.binarySearch(examIdsArr, i)) {
                    cExamIds.add(i);
                }
            }
        }
        if (!cExamIds.isEmpty()) exams.addAll(examService.selectBatchIds(cExamIds));
        var categoryMap = categories.stream().collect(Collectors.groupingBy(CategoryModel::getExamId));
        Long[] cids = null;
        if (userContext.loginStatus() && !categories.isEmpty()) {
            cids = categoryService.userHaveCategoryTree(userId).stream().sorted(Long::compareTo).toArray(Long[]::new);
        }
        for (var exam : exams) {
            var cs = categoryMap.get(exam.getId());
            if (CollUtil.isEmpty(cs)) continue;
            if (cids != null) {
                cs.sort(IndustryServiceImpl.permissionComparator(cids));
                for (var c : cs) {
                    c.setAuthFlag((byte) (CommonUtil.binarySearch(cids, c.getId()) ? 0x01 : 0x00));
                }
            }
            exam.setCategorys(CommonUtil.listData2Tree(cs));
        }
        var userExam = examService.userHaveExam(localGroupId, userGroupId, userId);
        if (!userExam.getAll().isEmpty())
            exams.sort(IndustryServiceImpl.permissionComparator(userExam.getAll().toArray(String[]::new)));
        if (ExamModel.GJTK.equals(examType)) {
            examAuthFlag(userExam, exams);
            return exams;
        }
        var industryIds = exams.stream().map(ExamModel::getIndustryId).distinct().toList();
        if (industryIds.isEmpty()) return null;
        var userIndustry = industryService.userHaveIndustry(localGroupId, userGroupId, userId);
        var examMap = exams.stream().collect(Collectors.groupingBy(ExamModel::getIndustryId));
        var g = userIndustry.getGroup().toArray(String[]::new);
        var u = userIndustry.getUser().toArray(String[]::new);
        var a = userIndustry.getAll().toArray(String[]::new);
        return industryService.selectBatchIds(industryIds)
                .stream().peek(v -> {
                    v.setExams(examMap.get(v.getId()));
                    byte r = CommonUtil.binarySearch(u, v.getId()) ? (byte) 0x01 : 0x00;
                    r |= CommonUtil.binarySearch(g, v.getId()) ? 0x02 : 0x00;
                    v.setAuthFlag(r);
                })
                .sorted(IndustryServiceImpl.permissionComparator(a))
                .toList();
    }
    @GetMapping("/hot-exam")
    @Roles
    public ExamModel hotExam(){
      return  examMapper.hotExamModel();
    }
    @Operation(summary = "提交问题建议")
    @PostMapping("/submit-suggestion")
    public void submitSuggestion(@RequestBody ProblemsModel problemModel) throws Exception{
//      /exam/exam/submit-suggestion
        String s = userContext.currentUserId();
        problemModel.setUserId(s);
        problemModel.setCreatedAt(LocalDateTime.now());
        problemService.insertQuestion(problemModel);
    }
    @ApiResult
    @GetMapping("pk-list")
    public Page<PkRecords> getRecords( Integer pageSize, Integer pageNum, @RequestParam(required = false, defaultValue = "") String username, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime startTime, @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime) {
//        QueryWrapper<PkRecords> queryWrapper = new QueryWrapper<>();
//        queryWrapper.between(startTime != null && endTime != null, "start_time", startTime, endTime)
//                .and(wrapper -> {
//                    if (!username.isEmpty()) {
//                        wrapper.like("user_name", username)
//                                .or()
//                                .like("other_name", username);
//                    }
//                });

        Page<PkRecords> page = new Page<PkRecords>(1, 10);
//        QueryWrapper<ExamModel> queryWrapper = new QueryWrapper<>();
        return pkRecordsService.recordsPage(page, username, startTime, endTime);
//        return null;
    }

    // 是否还在pk中，判断依据为内存列表是否有对应的session
    private Boolean isPking(String examId) {
        Queue<WebSocketSession> waitingPlayers = webSocketHandler.getWaitingPlayers();
        for (WebSocketSession session : waitingPlayers) {
            if (session.getAttributes().get("examId").equals(examId) && session.getAttributes().get("userId").equals(userContext.currentUserId())) {
                return true;
            }
        }
        return false;
    }
//    @GetMapping("/test")
//    public String test(HttpServletResponse res){
//        res.setStatus(201);
//        return null;
//    }
}
